using System;
using System.Collections.Generic;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;

namespace Dida
{
	internal class TraitBuilder
	{
		private Type type;
		private List<TraitProperty> classProps = new List<TraitProperty>();
		private List<TraitProperty> localProps = new List<TraitProperty>();

		public TraitBuilder(Type type)
		{
			this.type = type;
		}

		public Type Type
		{
			get { return type; }
		}

		public void CreateImplementation(TraitClassCodeGen classCodeGen)
		{
			TraitCodeGen traitCodeGen = new TraitCodeGen(classCodeGen, type);
			classCodeGen.AddTrait(traitCodeGen);

			foreach (TraitProperty prop in classProps)
				traitCodeGen.AddTraitClassProperty(prop);
			foreach (TraitProperty prop in localProps)
				traitCodeGen.AddTraitLocalProperty(prop);
		}

		public void AddClassProperty(TraitProperty prop)
		{
			classProps.Add(prop);
		}

		public void AddLocalProperty(TraitProperty prop)
		{
			localProps.Add(prop);
		}

		public bool Validate(TraitClassBuilder.ErrorList errors)
		{
			bool success = true;

			// there must be constructor with zero parameters, so the
			// GetConstructor method shouldn't return null, or if it does,
			// there shouldn't be any other constructors.
			if (type.GetConstructor(new Type[0]) == null &&
				type.GetConstructors().Length != 0)
			{
				errors.Add(new TraitClassBuilder.ConstructorParamsError(type));
				success = false;
			}

			foreach (TraitProperty prop in classProps)
				if (!prop.Validate(errors))
					success = false;
			foreach (TraitProperty prop in localProps)
				if (!prop.Validate(errors))
					success = false;
			return success;
		}
	}

	public partial class TraitClassBuilder
	{
		string name;

		List<TraitBuilder> traits = new List<TraitBuilder>();

		List<Implementation> implementations = new List<Implementation>();
		SortedDictionary<MethodFootprint, List<Declaration>>
			declarations = new SortedDictionary<MethodFootprint, List<Declaration>>();

		TraitClassCodeGen classCodeGen;

		public TraitClassBuilder(string name)
		{
			this.name = name;
		}

		/// <summary>
		/// Adds a trait to this trait.
		/// </summary>
		/// <remarks>
		/// The type of the trait must be a type that's derived from 
		/// the <see cref = "Trait"/> class, and it must have a constructor
		/// with no arguments.
		/// </remarks>
		/// <param name="traitType">The type of the trait.</param>
		/// <returns>The index of this trait. This is the index
		/// that can be used in the <see cref = "TraitInstance.GetTrait(int)"/>
		/// method.</returns>
		public int AddTrait(Type traitType)
		{
			Debug.Assert(traitType.IsSubclassOf(typeof(Trait)),
				"A trait must be derived from the Trait class");

			TraitBuilder traitBuilder = new TraitBuilder(traitType);
			traits.Add(traitBuilder);

			ReadImplementations(traitType);
			ReadDeclarations(traitType);

			ReadProperties(traitBuilder, traitType);

			return traits.Count - 1;
		}

		private void ReadImplementations(Type traitType)
		{
			foreach (MethodInfo method in traitType.GetMethods())
			{
				object[] attribs = method.GetCustomAttributes(
					typeof(TraitImplementMethodAttribute), true);
				if (attribs.Length == 1)
				{
					// if the method has a TraitImplement attribute,
					// we will add it to the implementedMethods list
					implementations.Add(new Implementation(traitType, method));
				}
				else
				{
					Debug.Assert(attribs.Length == 0, "You shouldn't specify the " +
						"TraitImplementMethod attribute more than once on a single method");
				}
			}
		}

		private void ReadDeclarations(Type traitType)
		{
			foreach (MethodInfo method in traitType.GetMethods())
			{
				object[] attribs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				if (attribs.Length == 1)
				{
					Debug.Assert(method.IsAbstract || method.IsVirtual,
						"A trait method declaration must be either abstract or virtual.");

					Declaration decl = new Declaration(traitType, method);
					MethodFootprint footprint = new MethodFootprint(method);

					List<Declaration> declsList;
					if (!declarations.TryGetValue(footprint, out declsList))
					{
						declsList = new List<Declaration>();
						declarations.Add(footprint, declsList);
					}
					declsList.Add(decl);
				}
				else
				{
					Debug.Assert(attribs.Length == 0,
						"You shouldn't specify the TraitDeclareMethod " +
						"attribute more than once on a single event");
				}
			}
		}

		private void ReadProperties(TraitBuilder builder, Type traitType)
		{
			foreach (PropertyInfo prop in traitType.GetProperties())
			{
				TraitPropertyAttribute[] attrs = (TraitPropertyAttribute[])
					prop.GetCustomAttributes(typeof(TraitPropertyAttribute), true);

				foreach (TraitPropertyAttribute attr in attrs)
				{
					switch (attr.Scope)
					{
						case TraitPropertyScope.Class:
							builder.AddClassProperty(new TraitProperty(prop));
							break;

						case TraitPropertyScope.Trait:
							builder.AddLocalProperty(new TraitProperty(prop));
							break;

						case TraitPropertyScope.Scoped:
							throw new NotImplementedException();

						default:
							Debug.Fail("Illegal scope used in TraitExternalProperty attribute on " +
								prop.ReflectedType.FullName + "." + prop.Name);
							break;
					}
				}
			}
		}

		public TraitClass Compile(out ErrorList errors)
		{
			errors = new ErrorList();

			if (!CheckCollisions(errors))
				return null;

			if (!CheckInheritenceCollisions(errors))
				return null;

			if (!ResolveMethods(errors))
				return null;

			if (!Validate(errors))
				return null;

			errors = null;
			return CreateImplementations();
		}

		public TraitClass Compile()
		{
			ErrorList errors;
			TraitClass ret = Compile(out errors);
			if (ret == null)
			{
				throw new Exception("Couldn't compile the trait class: \n" +
					errors.GetConcattedMessages(1) + ".");
			}

			return ret;
		}

		private bool CheckCollisions(ErrorList errors)
		{
			bool success = true;
			for (int i = 0; i < traits.Count; i++)
				for (int j = i + 1; j < traits.Count; j++)
				{
					if (traits[i].Type == traits[j].Type)
					{
						errors.Add(new TraitDuplicatedError(traits[i].Type));
						success = false;
					}
				}
			return success;
		}

		private bool CheckInheritenceCollisions(ErrorList errors)
		{
			Dictionary<Type, List<Type>> inheritenceGroups = new Dictionary<Type, List<Type>>();

			bool success = true;
			for (int i = 0; i < traits.Count; i++)
			{
				Type curTraitType = traits[i].Type;

				// find a group that this type can belong to
				bool groupFound = false;
				foreach (KeyValuePair<Type, List<Type>> inheritingTrait in inheritenceGroups)
				{
					if (curTraitType.IsSubclassOf(inheritingTrait.Key))
					{
						inheritingTrait.Value.Add(curTraitType);
						groupFound = true;
						break;
					}
				}

				if (!groupFound)
				{
					// find existing groups, who's major type 
					// (highest in the inheritence tree) type is
					// a subclass of the current type. If we find any,
					// we remove them from the inheritingTraits list, 
					// and add them to a new entry, which has the current type
					// as it's major type.
					List<Type> collidingTraits = new List<Type>();
					collidingTraits.Add(curTraitType);

					List<Type> toRemove = new List<Type>();
					foreach (KeyValuePair<Type, List<Type>> inheritingTrait in inheritenceGroups)
					{
						if (inheritingTrait.Key.IsSubclassOf(curTraitType))
						{
							collidingTraits.AddRange(inheritingTrait.Value);

							toRemove.Add(inheritingTrait.Key);
						}
					}

					inheritenceGroups[curTraitType] = collidingTraits;

					foreach (Type t in toRemove)
						inheritenceGroups.Remove(t);
				}
			}

			// if there are any traits that also have a base type in 
			// this traitclass, we "throw" an error
			foreach (KeyValuePair<Type, List<Type>> group in inheritenceGroups)
			{
				if (group.Value.Count > 1)
				{
					// we found an error
					errors.Add(new TraitsInheritError(group.Key, group.Value.ToArray()));
					success = false;
				}
			}

			return success;
		}

		private bool ResolveMethods(ErrorList errors)
		{
			bool success = true;

			// resolve the implementations
			foreach (Implementation impl in implementations)
			{
				List<Declaration> decls;
				if (declarations.TryGetValue(new MethodFootprint(impl.MethodInfo), out decls))
				{
					foreach (Declaration decl in decls)
						decl.AddImplementation(impl);
				}
				else if (impl.NeedsDeclaration)
				{
					errors.Add(new UnresolvedImplementationError(impl.MethodInfo));
					success = false;
				}
			}

			return success;
		}

		private bool Validate(ErrorList errorList)
		{
			bool success = true;

			foreach (TraitBuilder trait in traits)
				if (!trait.Validate(errorList))
					success = false;

			foreach (List<Declaration> decls in declarations.Values)
				foreach (Declaration decl in decls)
					if (!decl.Validate(errorList))
						success = false;

			return success;
		}

		private TraitClass CreateImplementations()
		{
			classCodeGen = new TraitClassCodeGen(name, traits.Count);

			Dictionary<Type, int> typeToIndex = new Dictionary<Type, int>();
			int index = 0;
			foreach (TraitBuilder trait in traits)
			{
				trait.CreateImplementation(classCodeGen);

				typeToIndex[trait.Type] = index;
				index++;
			}

			// link declarations to implementations
			foreach (KeyValuePair<MethodFootprint, List<Declaration>>
				declsByFootprint in declarations)
			{
				foreach (Declaration decl in declsByFootprint.Value)
				{
					if (decl.HasImplementations)
					{
						int declTraitIndex = typeToIndex[decl.Type];
						TraitCodeGen declTrait = classCodeGen.GetTrait(declTraitIndex);
						TraitDeclarationCodeGen declCodeGen =
							declTrait.AddDeclaration(decl.MethodInfo);

						foreach (Implementation impl in decl.Implementations)
						{
							declCodeGen.AddImplementation(impl.ImplementingType, impl.MethodInfo);
						}
					}
				}
			}

			classCodeGen.Finish();
			return new TraitClass(classCodeGen.MainType);
		}

		private class Declaration
		{
			Type type;
			MethodInfo method;
			TraitDeclareMethodAttribute attribute;
			TraitMethodRequirement requirement;

			List<Implementation> implementations = new List<Implementation>();

			public Declaration(Type type, MethodInfo method)
			{
				this.type = type;
				this.method = method;

				object[] attribs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				Debug.Assert(attribs.Length == 1);

				attribute = (TraitDeclareMethodAttribute)attribs[0];

				if (method.IsAbstract)
					requirement = TraitMethodRequirement.MustImplement;
				else
				{
					Debug.Assert(method.IsVirtual);
					requirement = TraitMethodRequirement.Optional;
				}
			}

			public void AddImplementation(Implementation impl)
			{
				implementations.Add(impl);
			}

			public bool Validate(ErrorList errorList)
			{
				if (attribute.Acceptance == TraitMethodAcceptance.SingleImplementation
					&& implementations.Count > 1)
				{
					// the trait wants a single implementation, but multiple 
					// are provided, so we throw an error
					errorList.Add(new TraitClassBuilder.MultipleImplementationsError(method));
					return false;
				}

				if (requirement == TraitMethodRequirement.MustImplement &&
					implementations.Count == 0)
				{
					// an implementation is required, but none is provided, 
					// so we throw an error.
					errorList.Add(new TraitClassBuilder.NoImplementationError(method));
					return false;
				}

				return true;
			}

			public string Name
			{
				get { return method.Name; }
			}

			public Type Type
			{
				get { return type; }
			}

			public MethodInfo MethodInfo
			{
				get { return method; }
			}

			public List<Implementation> Implementations
			{
				get { return implementations; }
			}

			public bool HasImplementations
			{
				get { return implementations.Count != 0; }
			}
		}

		private class Implementation
		{
			Type type;
			MethodInfo methodInfo;
			bool needsDeclaration;

			public Implementation(Type type, MethodInfo methodInfo)
			{
				this.type = type;
				this.methodInfo = methodInfo;

				object[] attrs = methodInfo.GetCustomAttributes(
					typeof(TraitImplementMethodAttribute), true);
				Debug.Assert(attrs.Length == 1);
				TraitImplementMethodAttribute attr = (TraitImplementMethodAttribute)attrs[0];
				needsDeclaration = attr.NeedsDeclaration;
			}

			public Type ImplementingType
			{
				get { return type; }
			}

			public MethodInfo MethodInfo
			{
				get { return methodInfo; }
			}

			public bool NeedsDeclaration
			{
				get { return needsDeclaration; }
			}
		}
	}
}